﻿#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

dynamic memory manage

一；为什么要动态内存管理
1.int a = 20：在栈空间上开辟四个字节
char arr[] = {0}；在栈空间上开辟10个字节的连续空间
上述的开辟空间的方式有两个特点
1。空间开辟大小是固定的
2。数组 在声明的时候，有时候我需要的空间大小在程序运行的时候才能知道
那数组的方式就不能满足了
C语言就引入了动态内存开辟，让程序员自己可以申请和释放空间




二；malloc 和 free
2.1malloc(头文件<stdlib.h>)
动态内存开辟函数malloc：void * mallloc (size_t size);
malloc;向内存申请一块连续可用的空间，并返回指向这块空间的指针
size _t size；表示开辟多大字节的空间
-如果开辟成功，返回一个指向开辟好空间的指针
-如果开辟失败，返回一个NULL指针，所以malloc返回值一定要检查
-返回类型是void*，所以开辟空间的指针类型用户可以自己决定
-如果参数size为0，malloc的行为是未定义的的，取决于编译器
开辟10个整型的内存空间：void * malloc (10 * size(int));

2.2free专门用来做动态内存的释放和回收的
void free (void * ptr);  头文件<stdlib.h>
-参数ptr，如果指向的空间是动态内存开辟的，则会释放，不是则行为是未定义的
-参数ptr如果是NULL指针，则函数声明也不做
！！！注；释放完后，ptr的地址还在，则成为野指针，所以要ptr = NULL

#include <stdlib.h>

malloc的使用
int main()
{

	创建10个整型的内存空间
	int *i = (int*) malloc(10 * sizeof(int));
	if (i == NULL)
	{
		perror("malloc");
		return 1;
	}
	int a = 0;
	for (a = 0; a < 10; a++)
	{
		i[a] = 1+a;
		printf("%d", i[a]);
	}
	return 0;
}



malloc开辟失败
int main()
{

	//创建规定的整数最大值
	int *i = (int*) malloc(1000000000000000000);
	if (i == NULL)
	{
		//空间开辟失败
		perror("malloc");
		return 1;
	}
	int a = 0;
	for (a = 0; a < 10; a++)
	{
		i[a] = 1+a;
		printf("%d", i[a]);
	}
	return 0;
}



free使用
int main()
{

	//创建10个整型的内存空间
	int *i = (int*) malloc(10 * sizeof(int));
	if (i == NULL)
	{
		perror("malloc");
		return 1;
	}
	int a = 0;
	for (a = 0; a < 10; a++)
	{
		i[a] = 1+a;
		printf("%d", i[a]);
	}
	free(i);//此时p指向的空间已经释放不存在了，但p指针还存在，p成为了野指针
	i = NULL;//所以要用NULL把野指针变掉

	return 0;
}

所以malloc和free最好一起用




三；calloc和realloc
3.1。calloc也是用来动态内存分配的
void * calloc (size_t num,size_t size);
-功能是为num个大小为size的元素开辟一块内存空间，！！！并把空间的每个字节初始化为0！！！
例；开辟10个整型空间； void * calloc (10,sizeof(int));
区别于malloc的是，calloc会在返回地址前把申请的空间中每个字节初始化为0


calloc的使用
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d", p[i]);//0000000000
		//这就是calloc创建内存空间后并对空间的每个字符初始化为0
	}
	//calloc创建的内存空间也要用free释放
	free(i);
	i = NULL;
	return 0;
}







3.3 realloc
-有时我们发现过去申请的动态内存空间太小了或者太大了，这时realloc函数就可以做到对
动态开辟内存大小的调整
void * realloc(void * ptr,size_t size)
-ptr;是要调整的动态内存的地址
-size； 调整之后的大小（调整为多大或多小）
-返回值为调整之后的内存起始地址
-realoc在调整内存空间的时候有两种情况
1；原有空间之后有足够大的空间
2；原有空间之后没有足够大的空间
情况1；之间在原有空间之后扩张空间，原来空间的数据不变，返回原有内存的起始地址
情况2；在堆空间上另找一个合适大小的连续空间来使用，原来空间被释放，返回一个新的起始地址


realloc的使用
int main()
{
	//先创建一个动态内存空间
	int*  p1 = (int*)calloc(10, sizeof(int));
	if (p1 == NULL)//判断是否创建成功
	{
		perror("calloc");
		return 1;
	}
	//发现空间不够用，用realloc扩展这个内存空间
	int* p2 = realloc(p1, 20 * sizeof(int));
	if (p2 == NULL)//判断扩展是否成功
	{
		perror("realloc");
		return 2;
	}
	//扩展成功后
	p1 = p2;
	int i = 0;
	for (i = 0; i < 20; i++)
	{
		p1[i] = 1 + i;
		printf("%d ", p1[i]);
	}
	//释放空间
	free(p1);
	p1 = NULL;
	return 0;
}







四；常见的动态内存错误

4.1对NULL解引用操作
不嫩对NULL进行解引用或者+-的操作
void test()
{
	int* p = (int*)malloc(INT_MAX / 4);//可能开辟失败，开辟失败返回NULL
	*p = 20;//如果p的值是NULL，就会有问题
	free(p);
}


4.2对动态开辟空间的越界访问
void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当i是10的时候越界访问
		//越界访问会造成野指针
	}
	free(p);
}



4.3对非动态开辟内存使用free释放
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	free(p);//p指向的是arr的数组首元素地址，，arr数组空间不是动态内存开辟的，所以不能用free释放
	return 0;
}


4.4使⽤free释放⼀块动态开辟内存的⼀部分
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p = 1 + i;
		p++;
		//这样p指向的位置就改变了
	}
	free(p);//这样释放会报错
	p = NULL;
	return 0;
}



4.5对同一块动态内存多次释放
int main()
{
	int* p = (int*)calloc(10,sizeof(int));
	if (p == NULL)
	{
		perror("calloc");
		return 0;
	}
	free(p);
	free(p);//已经释放过就不能再释放了
	p = NULL;
	return 0;
}



4.6动态开辟内存忘记释放（内存泄露）
void test()
{
	int flag = 0;
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	if (flag = 1)
	{
		return;//如果开辟内存空间成功并再这里返回函数，那么动态开辟内存就没有释放
		//虽然程序结束后也会释放，但万一程序很长才结束，那么这块内存空间就找不到了、
		//这就是内存泄露
	}
	free(p);
	p = NULL;
}
int main()
{
	test();
	return 0;
}
忘记释放不再使用的动态开辟的空间会造成内存泄露
所以动态开辟内存一定要释放！！！






五；动态内存经典笔试题分析



5.1
void GetMemory(char* p)//p也指向空的内存空间
{
	p = (char*)malloc(100);
	//这里动态开辟的内存空间收地址给p并不会改变str
}
void Test(void)
{
	char* str = NULL;//str指向空的内存空间
	GetMemory(str);//这里是传值调用
	strcpy(str, "hello world");//对NULL解引用会报错
	printf(str);
	free(str);
  str = NULL;
}
int main()
{
	Test();
	return 0;
}

修改
void GetMemory(char** p)//二级指针
{
	*p = (char*)malloc(100);
	//*p就是str
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);//把指针str的指针传过去
	strcpy(str, "hello world");
	printf(str);
  free(str)
  str = NULL;
}
int main()
{
	Test();
	return 0;
}



5.2
char *GetMemory(void)
{
	char p[] = "hello world";
	return p;//返回数组p的首元素地址
}
//函数结束后内存空间会自动释放，所以数组p创建的内存上的东西都会消失但p这个指针不会消失
void Test(void)
{
	char* str = NULL;
	str = GetMemory();//str成为一个野指针
	printf(str);//所以现在str指向的空间上的内容不是“hello world”
}
int main()
{
	Test();
	return 0;
}
总结；就是函数结束后会释放内存，所以内存空间上的东西都会消失，但内存地址是一直在的
如果返回地址是可以，但是地址指向的空间不存在了，是也野指针，所以不能进行解引用操作



5.3;内存泄露的案例
void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);//目前都没有问题，但是str指向的内存空间是malloc开辟的动态内存空间，所以要free释放


	//修改
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}



5.4非法访问的案例
void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);//释放malloc动态开辟的内存空间，所以现在str后面的不属于它了，它是野指针
	if (str != NULL)
	{
		strcpy(str, "world");//str是野指针，后面空间不属于它，这样会造成非法访问!!!
		printf(str);
	}
}
int main()
{
	Test();
	return 0;
}








六；柔性数组
6.1.C99中，结构体中最后一个成员允许是未知大小的数组，这个数组就叫做柔性数组
什么是柔性数组；-在结构体中
                -组后一个成员
                -成员是未知大小的数组
struct s1
{
	int i;
	int arr1[];//未知大小的数组，arr1就是柔性数组
};
struct s2
{
	int j;
	int arr2[0];//未知大小的数组，arr2就是柔性数组
};
s1和s2都是柔性数组


6.2.柔性数组的特点
-结构体中的柔性数组成员前面必须至少有一个其他成员
-sizeof返回的这种结构体大小不包括柔性数组内存
-包含柔性数组成员的结构用malloc（）函数进行内存的动态分配
并且分配的内存应该大于结构体的大小以适应柔性数组的预期大小

测试柔性数组成员的结构体大小
struct s1
{
	int i;
	int arr1[];//未知大小的数组，arr1就是柔性数组
};
int main()
{
	printf("%d", sizeof(struct s1));//4,就没有计算柔性数组的大小
	return 0;
}


柔性数组的使用
struct s1
{
	int i;
	int arr[];
};
int main()
{
	创捷结构体内存空间
	struct s1* p = (struct s1*)malloc(sizeof(int) + 20 * sizeof(int));
	判断是否创建成功
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}

	p->i = 100;
	给柔性数组赋值
	int j = 0;
	for (j = 0; j < 20; j++)
	{
		p->arr[j] = 1 + j;
		printf("%d ", p->arr[j]);
	}
	释放动态开辟的内存空间
	free(p);
	p = NULL;
	return 0;
}


int main()
{
	char a = "a";
	printf("%d", a);
	return 0;
}